package util.http

import java.net.InetAddress
import java.net.InetSocketAddress
import java.security.cert.X509Certificate
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.HttpMethod
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager
import org.apache.commons.httpclient.cookie.CookiePolicy
import org.apache.commons.httpclient.methods.PostMethod
import org.apache.commons.httpclient.params.HttpClientParams
import org.apache.commons.httpclient.params.HttpConnectionParams
import org.apache.commons.httpclient.params.HttpMethodParams
import org.apache.commons.httpclient.protocol.Protocol
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
import org.apache.commons.httpclient.auth.AuthScope
import org.apache.commons.httpclient.UsernamePasswordCredentials
import org.apache.commons.httpclient.methods.GetMethod
import java.util.Locale

object Https extends Object with Logable with WithConsoleLog {

  private val connectionManager = new MultiThreadedHttpConnectionManager {
    val params = this.getParams
    params.setDefaultMaxConnectionsPerHost(200)
    params.setMaxTotalConnections(500)
    params.setConnectionTimeout(10000)
    params.setSoTimeout(10000)
  }
  private val clientParams = new HttpClientParams {
    setCookiePolicy(CookiePolicy.IGNORE_COOKIES)
  }
  Protocol.registerProtocol("https", new Protocol("https", new ProtocolSocketFactory {

    private val sslcontext: SSLContext = {
      val v = SSLContext.getInstance("SSL")
      v.init(null, Array(new X509TrustManager {
        def checkClientTrusted(chain: Array[X509Certificate], authType: String) = {}
        def checkServerTrusted(chain: Array[X509Certificate], authType: String) = {}
        def getAcceptedIssuers = Array[X509Certificate]()
      }), new java.security.SecureRandom())
      v
    }

    override def createSocket(host: String, port: Int) =
      sslcontext.getSocketFactory().createSocket(host, port)

    override def createSocket(host: String, port: Int,
      clientHost: InetAddress, clientPort: Int) = sslcontext.getSocketFactory().createSocket(host, port, clientHost,
      clientPort)

    override def createSocket(host: String, port: Int,
      localAddress: InetAddress, localPort: Int,
      params: HttpConnectionParams) =
      {
        if (params == null) {
          throw new IllegalArgumentException(
            "Parameters may not be null");
        }
        val timeout = params.getConnectionTimeout();
        val socketfactory = sslcontext.getSocketFactory();
        if (timeout == 0) {
          socketfactory.createSocket(host, port, localAddress, localPort);
        } else {
          val socket = socketfactory.createSocket();
          val localaddr = new InetSocketAddress(localAddress, localPort)
          val remoteaddr = new InetSocketAddress(host, port)
          socket.bind(localaddr);
          socket.connect(remoteaddr, timeout);
          socket;
        }
      }
  }, 443))

  private def apply[R](fn: HttpClient => R, proxy: (String, Int) = null, credentials: (String, String) = null) =
    {
      val client = new HttpClient(clientParams, connectionManager)
      proxy match {
        case (ip, port) if (ip != null && ip.nonEmpty && port > 0) =>
          client.getHostConfiguration().setProxy(ip, port)
        case _ =>
      }
      credentials match {
        case (name, pwd) if (name != null && pwd != null) =>
          client.getParams().setAuthenticationPreemptive(true)
          client.getState().setCredentials(AuthScope.ANY,
            new UsernamePasswordCredentials(name, pwd))
        case _ =>
      }
      fn(client)
    }

  private def execUntilSuccuss[T <: HttpMethod, R](method: T, proxy: (String, Int) = null, credentials: (String, String) = null)(otherMethodAction: T => R) =
    Retry(
      exec(method, proxy, credentials) { otherMethodAction },
      { e: Throwable =>
        log.error("", e)
      })()
  private def exec[T <: HttpMethod, R](method: T, proxy: (String, Int) = null, credentials: (String, String) = null)(otherMethodAction: T => R) =
    apply({ c =>
      try {
        c.executeMethod(method)
        otherMethodAction(method)
      } finally {
        method.releaseConnection
      }
    }, proxy, credentials)
  private def postMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]) =
    new PostMethod(baseUrl) {
      params.foreach {
        case (k, v) =>
          require(k != null && v != null)
          addParameter(k.toString, v.toString)
      }
      getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 30000)
      getParams().setContentCharset("UTF-8")
      if (headers.nonEmpty)
        headers.foreach {
          case (k, v) =>
            setRequestHeader(k, v)
        }
    }
  private def getMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]) =
    new GetMethod(baseUrl + (if (params.nonEmpty)
      "?" + params.filter { case (k, v) => k != null && v != null }.map {
      case (k, v) =>
        s"$k=${v}"
    }.mkString("&")
    else "")) {

      getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 30000)
      getParams().setContentCharset("UTF-8")
      if (headers.nonEmpty)
        headers.foreach {
          case (k, v) =>
            setRequestHeader(k, v)
        }
    }
  def post(baseUrl: String, params: (Any, Any)*)(proxy: (String, Int) = null, credentials: (String, String) = null, headers: Iterable[(String, String)] = Nil) =
    exec(postMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]), proxy, credentials) { m =>
      m.getResponseBodyAsString
    }
  def postRes(baseUrl: String, params: (Any, Any)*)(proxy: (String, Int) = null, credentials: (String, String) = null, headers: Iterable[(String, String)] = Nil) =
    execUntilSuccuss(postMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]), proxy, credentials) { m =>
      m.getResponseBodyAsString
    }
  def get(baseUrl: String, params: (Any, Any)*)(proxy: (String, Int) = null, credentials: (String, String) = null, headers: Iterable[(String, String)] = Nil) =
    exec(getMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]), proxy, credentials) { m =>
      m.getResponseBodyAsString
    }
  def getRes(baseUrl: String, params: (Any, Any)*)(proxy: (String, Int) = null, credentials: (String, String) = null, headers: Iterable[(String, String)] = Nil) =
    execUntilSuccuss(getMethod(baseUrl: String, params: Seq[(Any, Any)], headers: Iterable[(String, String)]), proxy, credentials) { m =>
      m.getResponseBodyAsString
    }
}